<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <canvas id="canvas"></canvas>
  <script>
    class Snake {
      constructor(canvas, { width = 400, height = 400 } = {}) {
        this.canvas = canvas;
        this.ctx = canvas.getContext("2d");
        this.width = width;
        this.height = height;
        // 格子宽度
        this.gap = 10;
        // 初始化
        this.init();
      }
      init () {
        const { canvas, width, height } = this;
        canvas.width = width;
        canvas.height = height;
        // 画格子
        this.drawGrid();
        // 初始化蛇的坐标，位置固定
        this.snake = [
          { x: 10, y: 100 },
          { x: 20, y: 100 },
          { x: 30, y: 100 },
          { x: 40, y: 100 },
          { x: 50, y: 100 },
        ];
        // 初始化食物
        this.food = this.randomFood();
        // 绘制蛇
        this.drawSnake();
        // 绘制食物
        this.drawFood();
        // 移动
        this.move()
        // 方向，默认往右
        this.direction = "right";
        // 绑定操作
        this.bindEvent();
      }
      bindEvent () {
        // 键盘上的方向按键
        const keys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
        document.addEventListener("keydown", (event) => {
          // 规定只有按方向按键才起作用
          if (!keys.includes(event.key)) return
          // 不允许往反方向移动
          if (event.key === "ArrowUp" && this.direction !== "bottom") {
            this.direction = "top";
          } else if (event.key === "ArrowDown" && this.direction !== "top") {
            this.direction = "bottom";
          } else if (event.key === "ArrowLeft" && this.direction !== "right") {
            this.direction = "left";
          } else if (event.key === "ArrowRight" && this.direction !== "left") {
            this.direction = "right";
          }

          // 改变方向后要重新调用一下move函数
          this.move()
        });
      }
      randomFood () {
        const { width, height } = this;
        // 生成0-最大宽度之间的10倍的整数
        const x = Math.floor((Math.random() * width) / 10) * 10;
        const y = Math.floor((Math.random() * height) / 10) * 10;

        return { x, y };
      }
      move () {
        const { width, height, ctx, snake } = this;
        // 因为要多次调用，每次调用前要清空定时器
        clearInterval(this.timer);
        this.timer = setInterval(() => {
          // 更新蛇的坐标
          // 获取增加的节点坐标
          const { x, y } = this.updateSnake()
          // 吃的动作，把最新的头部坐标传进去
          this.handleEat(x, y)
          // 游戏结束
          if (this.isHitWall({ x, y }) || this.isEatSelf({ x, y })) {
            console.log('游戏结束')
            clearInterval(this.timer);
            return
          }
          // 尾巴去掉一格
          snake.shift();
          // 头部新增一格
          snake.push({ x, y });
          // 清空画布
          ctx.clearRect(0, 0, width, height);
          // 画格子
          this.drawGrid();
          // 重新画蛇
          this.drawSnake();
          // 重新绘制食物
          this.drawFood();
        }, 200);
      }
      // 吃
      handleEat (x, y) {
        const { snake, direction, gap, ctx } = this
        if (x === this.food.x && y === this.food.y) {
          // 吃到了食物
          let { x, y } = snake[0];
          switch (direction) {
            case "right":
              x -= gap;
              break;
            case "left":
              x += gap;
              break;
            case "top":
              y += gap;
              break;
            case "bottom":
              y -= gap;
              break;
            default:
              break;
          }
          // 尾部增加一节
          snake.unshift({ x, y });
          // 清空食物
          ctx.clearRect(this.food.x, this.food.y, gap, gap);
          // 重新生成食物坐标
          this.food = this.randomFood();
        }
      }
      // 判断是否撞到边界
      isHitWall (head) {
        const { width, height } = this;
        // x轴大于最大宽度或小于最小宽度
        // y轴大于最大高度或小于最小高度
        return head.x >= width || head.x < 0 || head.y >= height || head.y < 0
      }

      // 判断是否吃到了自己
      isEatSelf (head) {
        const { snake } = this
        // 头部不可能和头部重合，只判断身体
        const body = snake.slice(1);
        // 头部和身体任何一个节点重合
        return body.some(item => item.x === head.x && item.y === head.y);
      }
      updateSnake () {
        const { snake, direction, gap } = this
        let { x, y } = snake.at(-1);
        switch (direction) {
          case "right":
            x += gap;
            break;
          case "left":
            x -= gap;
            break;
          case "top":
            y -= gap;
            break;
          case "bottom":
            y += gap;
            break;
          default:
            break;
        }
        return { x, y }
      }
      // 画格子
      drawGrid () {
        const { ctx, width, height, gap } = this;
        // 青绿色背景
        ctx.fillStyle = "#96c93d";
        ctx.fillRect(0, 0, width, height);
        // 白色线条
        ctx.lineWidth = 0.5;
        ctx.strokeStyle = "#fff";
        for (let i = 0; i < width; i += gap) {
          ctx.beginPath();
          ctx.moveTo(i, 0);
          ctx.lineTo(i, height);
          ctx.stroke();
          ctx.closePath();
        }
        for (let i = 0; i < height; i += gap) {
          ctx.beginPath();
          ctx.moveTo(0, i);
          ctx.lineTo(width, i);
          ctx.stroke();
          ctx.closePath();
        }
      }
      // 绘制蛇
      drawSnake () {
        const { ctx, snake, gap, width, height } = this;
        snake.forEach((item, index) => {
          // 蛇头部画个红色
          // 身体画黑色
          if (index === snake.length - 1) {
            ctx.fillStyle = "red";
          } else {
            ctx.fillStyle = "black";
          }
          ctx.fillRect(item.x + 1, item.y + 1, gap - 1, gap - 1);
        });
      }
      // 绘制食物
      drawFood () {
        const { ctx, food, gap } = this;
        const { x, y } = food;
        ctx.fillStyle = "#035c03";
        ctx.fillRect(x, y, gap, gap);
      }
    }
    window.onload = () => {
      new Snake(document.querySelector('canvas'))
    }
  </script>
</body>

</html>